﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Engine.Cameras;
using Engine.Light;
using Engine.Light.Deferred;

namespace Engine.SceneControl.Scene.DefRender
{
    public class DeferredRender : IRenderTechnic
    {
        
        private QuadRender quadRenderer;
        private RenderTarget2D colorRT;    //this Render Target will hold color and Specular Intensity
        private RenderTarget2D normalRT; //this Render Target will hold normals and Specular Power
        private RenderTarget2D depthRT; //finally, this one will hold the depth
        Effect clearBufferEffect;
        private SpriteBatch spriteBatch;
        private Vector2 halfPixel;
        private Effect directionalLightEffect;
        private RenderTarget2D lightRT;
        private Effect finalCombineEffect;
        private Effect pointLightEffect;
        private Model sphereModel;
        private EngineStuff engine;
        private Effect gbufferEffect;
        private bool debug = false;

        public DeferredRender(EngineStuff engine)            
        {
            this.engine = engine;
        }

        public void Initialization(IRender render, IWorld world, EngineStuff engine)
        {
            quadRenderer = new QuadRender(engine.GraphicsDevice);
            LoadContent();

        }

        public void ExecuteTechnic(IRender render, IWorld world)
        {
            Draw(engine.GetCurrentGameTime(), world);
        }

        public bool isDebug
        {
            set
            {
                this.debug = value;
            }
        }


        private void Draw(GameTime gameTime , IWorld world)
        {
            SetGBuffer();
            ClearGBuffer();

            DrawScene(world);

            ResolveGBuffer();

            DrawLights(world,gameTime);

            int halfWidth = engine.GraphicsDevice.Viewport.Width / 2;
            int halfHeight = engine.GraphicsDevice.Viewport.Height / 2;

            if (debug)
            {
                spriteBatch.Begin(SpriteBlendMode.None);
                spriteBatch.Draw(colorRT.GetTexture(), new Rectangle(0, 0, halfWidth, halfHeight), Color.White);
                spriteBatch.Draw(normalRT.GetTexture(), new Rectangle(0, halfHeight, halfWidth, halfHeight), Color.White);
                spriteBatch.Draw(depthRT.GetTexture(), new Rectangle(halfWidth, 0, halfWidth, halfHeight), Color.White);
                spriteBatch.End();
            }


        }

        private void DrawScene(IWorld world)
        {            
            
            engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
            engine.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            engine.GraphicsDevice.RenderState.AlphaBlendEnable = false;                        
                        
            gbufferEffect.Parameters["View"].SetValue(world.Camera.View);
            gbufferEffect.Parameters["Projection"].SetValue(world.Camera.Projection);            


            foreach (IObject item in world.Objects)
	            {
                foreach (ModelMesh mesh in item.Modelo.getModelMeshes(0))
	                    {
                            Matrix[] boneTransforms = item.Modelo.getBonesTransformation(0);

                                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                                    {
                                        gbufferEffect.Parameters["Texture"].SetValue(item.Modelo.getTexture());
                                        Matrix wld = item.getWorldMatrix();
                                        wld = Matrix.Multiply(wld, boneTransforms[mesh.ParentBone.Index]);
                                        gbufferEffect.Parameters["World"].SetValue(wld);
                                        gbufferEffect.Begin();
                                        gbufferEffect.CurrentTechnique.Passes[0].Begin();
                                        engine.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                                        engine.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                                        engine.GraphicsDevice.Indices = mesh.IndexBuffer;
                                        engine.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,meshPart.BaseVertex, 0,meshPart.NumVertices,meshPart.StartIndex,meshPart.PrimitiveCount);
                                        gbufferEffect.CurrentTechnique.Passes[0].End();
                                        gbufferEffect.End();

                                    }
		 
	                    }
            		 
	            } 
            

        }       

        
        private void SetGBuffer()
        {
            engine.GraphicsDevice.SetRenderTarget(0, colorRT);
            engine.GraphicsDevice.SetRenderTarget(1, normalRT);
            engine.GraphicsDevice.SetRenderTarget(2, depthRT);
        }
        private void ResolveGBuffer()
        {
            engine.GraphicsDevice.SetRenderTarget(0, null);
            engine.GraphicsDevice.SetRenderTarget(1, null);
            engine.GraphicsDevice.SetRenderTarget(2, null);
        }

        private void ClearGBuffer()
        {
            clearBufferEffect.Begin();
            clearBufferEffect.Techniques[0].Passes[0].Begin();
            quadRenderer.Render(Vector2.One * -1, Vector2.One);
            clearBufferEffect.Techniques[0].Passes[0].End();
            clearBufferEffect.End();
        }
        private void LoadContent()
        {
            halfPixel.X = 0.5f / (float)engine.GraphicsDevice.PresentationParameters.BackBufferWidth;
            halfPixel.Y = 0.5f / (float)engine.GraphicsDevice.PresentationParameters.BackBufferHeight;

            //get the sizes of the backbuffer, in order to have matching render targets   
            int backBufferWidth = engine.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = engine.GraphicsDevice.PresentationParameters.BackBufferHeight;

            colorRT = new RenderTarget2D(engine.GraphicsDevice, backBufferWidth,
                                                                    backBufferHeight, 1, SurfaceFormat.Color);
            normalRT = new RenderTarget2D(engine.GraphicsDevice, backBufferWidth,
                                                                    backBufferHeight, 1, SurfaceFormat.Color);
            depthRT = new RenderTarget2D(engine.GraphicsDevice, backBufferWidth,
                                                                    backBufferHeight, 1, SurfaceFormat.Single);
            lightRT = new RenderTarget2D(engine.GraphicsDevice, backBufferWidth,
                                                            backBufferHeight, 1, SurfaceFormat.Color);

            clearBufferEffect = engine.Content.Load<Effect>("..\\Content\\Effects\\Deferred\\ClearGBuffer");
            directionalLightEffect = engine.Content.Load<Effect>("..\\Content\\Effects\\Deferred\\DirectionalLight");
            finalCombineEffect = engine.Content.Load<Effect>("..\\Content\\Effects\\Deferred\\CombineFinal");
            pointLightEffect = engine.Content.Load<Effect>("..\\Content\\Effects\\Deferred\\PointLight");
            sphereModel = engine.Content.Load<Model>("..\\Content\\Models\\Dsphere");
            gbufferEffect = engine.Content.Load<Effect>("..\\Content\\Effects\\Deferred\\RenderGBuffer");
            spriteBatch = new SpriteBatch(engine.GraphicsDevice);

        }
        private void DrawDirectionalLight(ICamera camera,IList<ILight> lights)
        {
            //set all parameters
            directionalLightEffect.Parameters["colorMap"].SetValue(colorRT.GetTexture());
            directionalLightEffect.Parameters["normalMap"].SetValue(normalRT.GetTexture());
            directionalLightEffect.Parameters["depthMap"].SetValue(depthRT.GetTexture());            
            directionalLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
            directionalLightEffect.Parameters["halfPixel"].SetValue(halfPixel);
                        
            foreach (ILight item in lights)
            {
                if (item.LightType == LightType.Deferred_Directional)
                {
                    Engine.Light.Deferred.LightDirectional dl = item as Engine.Light.Deferred.LightDirectional;

                    directionalLightEffect.Parameters["lightDirection"].SetValue(dl.LightDirection);
                    directionalLightEffect.Parameters["Color"].SetValue(dl.Color.ToVector3());
                    
                    directionalLightEffect.Begin();
                    directionalLightEffect.Techniques[0].Passes[0].Begin();                    
                    quadRenderer.Render(Vector2.One * -1, Vector2.One);
                    directionalLightEffect.Techniques[0].Passes[0].End();
                    directionalLightEffect.End();

                }
                
            }
            
        }

        private void DrawLights(IWorld world, GameTime gameTime)
        {
            engine.GraphicsDevice.SetRenderTarget(0, lightRT);

            //clear all components to 0
            engine.GraphicsDevice.Clear(Color.TransparentBlack);
            engine.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            //use additive blending, and make sure the blending factors are as we need them
            engine.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            engine.GraphicsDevice.RenderState.SourceBlend = Blend.One;
            engine.GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            //use the same operation on the alpha channel
            engine.GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;
            engine.GraphicsDevice.RenderState.DepthBufferEnable = false;

            DrawDirectionalLight(world.Camera,world.Lights);

            DrawPointLight(world.Camera,world.Lights);

            
            engine.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            engine.GraphicsDevice.SetRenderTarget(0, null);

            //set the effect parameters
            finalCombineEffect.Parameters["colorMap"].SetValue(colorRT.GetTexture());
            finalCombineEffect.Parameters["lightMap"].SetValue(lightRT.GetTexture());
            finalCombineEffect.Parameters["halfPixel"].SetValue(halfPixel);

            finalCombineEffect.Begin();
            finalCombineEffect.Techniques[0].Passes[0].Begin();

            //render a full-screen quad
            quadRenderer.Render(Vector2.One * -1, Vector2.One);

            finalCombineEffect.Techniques[0].Passes[0].End();
            finalCombineEffect.End();
        }


        private void DrawPointLight(ICamera camera,IList<ILight> lights)
        {
            //set the G-Buffer parameters
            pointLightEffect.Parameters["colorMap"].SetValue(colorRT.GetTexture());
            pointLightEffect.Parameters["normalMap"].SetValue(normalRT.GetTexture());
            pointLightEffect.Parameters["depthMap"].SetValue(depthRT.GetTexture());
            pointLightEffect.Parameters["Projection"].SetValue(camera.Projection);
            pointLightEffect.Parameters["View"].SetValue(camera.View);
            pointLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            pointLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
            pointLightEffect.Parameters["halfPixel"].SetValue(halfPixel);

            foreach (ILight item in lights)
            {
                if (item.LightType == LightType.Deferred_Point)
                {
                    LightPoint pl = item as LightPoint;
                    //compute the light world matrix
                    //scale according to light radius, and translate it to light position
                    Matrix sphereWorldMatrix = Matrix.CreateScale(pl.LightRadius) * Matrix.CreateTranslation(pl.LightPosition);
                    pointLightEffect.Parameters["World"].SetValue(sphereWorldMatrix);

                    //light position
                    pointLightEffect.Parameters["lightPosition"].SetValue(pl.LightPosition);
                    pointLightEffect.Parameters["Color"].SetValue(pl.Color.ToVector3());
                    pointLightEffect.Parameters["lightRadius"].SetValue(pl.LightRadius);
                    pointLightEffect.Parameters["lightIntensity"].SetValue(pl.LightIntensity);

                    float cameraToCenter = Vector3.Distance(camera.Position, pl.LightPosition);

                    if (cameraToCenter < pl.LightRadius)
                        engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
                    else
                        engine.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

                    pointLightEffect.Begin();
                    pointLightEffect.Techniques[0].Passes[0].Begin();

                    foreach (ModelMesh mesh in sphereModel.Meshes)
                    {
                        foreach (ModelMeshPart meshPart in mesh.MeshParts)
                        {
                            engine.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                            engine.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                            engine.GraphicsDevice.Indices = mesh.IndexBuffer;
                            engine.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, meshPart.BaseVertex, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
                        }
                    }

                    pointLightEffect.Techniques[0].Passes[0].End();
                    pointLightEffect.End();



                }
            }

            engine.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;



        }

        #region IRenderTechnic Members

        
        public string TechnicName
        {
            get { return "DeferredTechnic"; }
        }

        #endregion
    }
}

